﻿package com.janisRadins.asDraw {
	import flash.display.GraphicsPath;
	import flash.geom.Point;
	/**
	 * @author Jānis Radiņš
	 */
	public class DrawUtils {
		/**
		 * Static method for joining several GraphicsPath objects into one
		 * @param	... paths	Array of GraphicsPath instances to be joined
		 * @return	new GraphicsPath instance which holds combination of data
		 */
		public static function joinGraphicPaths( ... paths):GraphicsPath {
			if (paths.length == 0) {
				return null;
			}
			var combined:GraphicsPath = new GraphicsPath();
			combined.commands = new Vector.<int>();
			combined.data = new Vector.<Number>();
			var path:GraphicsPath;
			for (var i:uint = 0; i < paths.length; i++) {
				path = paths[i] as GraphicsPath;
				combined.commands = combined.commands.concat(path.commands);
				combined.data = combined.data.concat(path.data);
			}
			return combined;
		}
		/**
		 * Method that moves GraphicsPath data for certain x and y distance
		 * @param	path	GraphicsPath instance thats should be modified
		 * @param	add		Point which keeps for how much GraphicsPath values should be shifted
		 * @return	GraphicsPath instance
		 */
		public static function moveGraphicPath(path:GraphicsPath, add:Point):GraphicsPath {
			for (var i:uint = 0; i < path.data.length; i++) {
				if (i % 2 == 0) {
					path.data[i] += add.x;
				} else {
					path.data[i] += add.y;
				}
			}
			return path;
		}
		/**
		 * Returns point on Cubic bezzier by ratio
		 * @param	t
		 * @param	p0
		 * @param	p1
		 * @param	p2
		 * @param	p3
		 * @return
		 */
		public static function getPointOnCubitBezzier(t:Number, p0:Number, p1:Number, p2:Number, p3:Number):Number {
			return Math.pow(1 - t, 3) * p0 + 3 * Math.pow(1 - t, 2) * t * p1 + 3 * (1 - t) * Math.pow(t, 2) * p2 + Math.pow(t, 3) * p3;
		}
		/**
		 * Method that calculates control point on quadratic bezzier by 3 points located at 0, 0.5 and 1 ratios.
		 * @param	p0	Curve start position
		 * @param	p1	Curve mid point
		 * @param	p2	Curve end position
		 * @return	Controll point for given inputs
		 */
		protected static function getQuadCurveCrtlPoint(p0:Number, p1:Number, p2:Number):Number {
			return ((p1 - p0) / 0.5 - 0.5 * (p2 - p0)) + p0;
		}
		/**
		 * Degrees to radians
		 * @param	angle Angle value in degrees
		 * @return	Angle value in radians
		 */
		public static function d2r(angle:Number):Number {
			return angle * (Math.PI / 180);
		}
		/**
		 * Radians to degrees
		 * @param	angle Angle value in radians
		 * @return 	Angle value in degrees
		 */
		public static function r2d(angle:Number):Number {
			return angle*(180/Math.PI);
		}
		/**
		 * Method that calculates quadratic curves of Ellipse/Circle or one or another segment.
		 * Only first param is obligatory - if rest are left empty full circle will be created.
		 * @param	radX		X radius of Ellipse
		 * @param	radY		Y radius of Ellipse
		 * @param	angle1		Ellipse segment start angle in radians
		 * @param	angle2		Ellipse segment end angle in radians
		 * @param	forward		Boolean value which indicates if segment should be created by adding value to start angle or opposite
		 * @param	rotation	Ellipse rotation in radians
		 * @return	Array with quadratic curves. Elements 0 and 1 reffer to moveTo command params 
		 * 			followed by (controlX, controlY, anchorX, anchorY)+
		 */
		public static function getEllipse(radX:Number, radY:Number = NaN, angle1:Number = 0, angle2:Number = 0, forward:Boolean = true, rotation:Number = 0):Array {
			//trace("getEllipse(" + radX + ", " + radY +", " + angle1 + ", " + angle2 + ", " + forward + ", " + rotation + ")");
			// If radY is NaN set it to same value as radX and draw circle
			if (isNaN(radY) == true) {
				radY = radX;
			}
			var delta:Number = (angle1 == angle2 ? Math.PI * 2 : getAngleDelta(angle1, angle2, forward));
			var steps:uint = Math.ceil(delta / (Math.PI / 4));
			if(steps > 100) {
				throw new Error(angle1 + " : " + angle2 + " : " + delta + " : "+ steps+" - "+(Math.PI / 4));
				return [];
			}
			var currentAngle:Number = angle1;
			var midAngle:Number;
			var nextAngle:Number;
			
			var sp:Point = null;
			var ep:Point = new Point();
			var cp:Point = new Point();
			
			var data:Array = new Array();
			
			for (var step:uint = 0; step <= steps; step++) {
				if (forward == true) {
					nextAngle = angle1 + Math.min(delta, (Math.PI / 4) * (step + 1));
					midAngle = currentAngle + (nextAngle-currentAngle) / 2;
				} else {
					nextAngle = angle1 - Math.min(delta, (Math.PI / 4) * (step + 1));
					midAngle = currentAngle - (currentAngle-nextAngle) / 2;
				}
				sp = (sp == null ? new Point(radX * Math.cos(currentAngle), radY * Math.sin(currentAngle)) : ep.clone());
				ep.x = radX * Math.cos(nextAngle);
				ep.y = radY * Math.sin(nextAngle);
				
				cp.x = getQuadCurveCrtlPoint(sp.x, radX * Math.cos(midAngle), ep.x);
				cp.y = getQuadCurveCrtlPoint(sp.y, radY * Math.sin(midAngle), ep.y);
				if (data.length == 0) {
					data.push(sp.x, sp.y);
				}
				data.push(cp.x, cp.y, ep.x, ep.y);
				currentAngle = nextAngle;
			}
			if (rotation != 0) {
				var point:Point;
				for (var i:uint = 0; i < data.length; i += 2) {
					point = rotatePoint(data[i], data[i + 1], rotation);
					data[i] = point.x;
					data[i + 1] = point.y;
				}
			}
			return data;
		}
		/**
		 * Method that calculates delta between angles
		 * @param	angle1	Start angle in radians
		 * @param	angle2	End angle in radians
		 * @param	forward	Direction of measurment (true - clockwise, false - counter clockwise). default true
		 * @return Delta between angles in radians, always positive value
		 */
		public static function getAngleDelta(angle1:Number, angle2:Number, forward:Boolean = true):Number {
			var m:Number = Math.max(Math.abs(angle1), Math.abs(angle2));
			var delta:Number = Math.abs((angle2 + m) - (angle1 + m));
			return forward ? delta : Math.PI * 2 - delta;
		}
		/**
		 * Method that rotates point by some angle
		 * @param	x		X coordinate of point
		 * @param	y		Y coordinate of point
		 * @param	angle	Angle by which point should be rotated around it's center (0, 0) point in radians
		 * @return	Point with calculated values
		 */
		public static function rotatePoint(x:Number, y:Number, angle:Number):Point {
			var radius:Number = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
			var angle:Number = Math.atan2(y, x) + angle;
			return new Point(radius * Math.cos(angle), radius * Math.sin(angle));
		}
	}
}